#region (c) 2014 Nguyễn, M. Hải - All rights reserved
/*
BLADE VIEW ENGINE

The MIT License (MIT)
Copyright (c) 2014 Nguyễn, M. Hải

Permission is hereby granted, free of charge, to any person obtaining a copy of this 
software and associated documentation files (the "Software"), to deal in the Software 
without restriction, including without limitation the rights to use, copy, modify, merge
, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 
persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
 substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
DEALINGS IN THE SOFTWARE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 30-Apr-14 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using Microsoft.CSharp;
using System.CodeDom.Compiler;

using Thn;
using Thn.Collections;
using System.Reflection;
using Thn.IO;
#endregion

namespace Thn.Web.Blade
{
    /// <summary>
    /// Responsible for compiling a BLADE template to class.
    /// </summary>
#if DEBUG || TESTING
    public
#else
    internal
#endif
 class BladeCodeCompiler : BladeCodeGenerator
    {
        #region Fields
        #endregion

        #region Properties

        #region Referenced Assemblies
        private List<string> mReferencedAssemblies = new List<string>();
        /// <summary>
        /// Gets or sets list of referenced assembly names.
        /// </summary>
        public List<string> ReferencedAssemblies
        {
            get { return mReferencedAssemblies; }
            set { mReferencedAssemblies = value; }
        }
        #endregion

        #region Generate In Memory
        private bool mGenerateInMemory = true;
        /// <summary>
        /// Gets or sets whether codes shall be generated to memory.
        /// <para>Default is true.</para>
        /// </summary>
        public bool GenerateInMemory
        {
            get { return mGenerateInMemory; }
            set { mGenerateInMemory = value; }
        }
        #endregion

        #region Silent Error
        private bool mSilentError = false;
        /// <summary>
        /// Gets or sets whether errors shall be silent or thrown as exception.
        /// <para>Default is false.</para>
        /// </summary>
        public bool SilentError
        {
            get { return mSilentError; }
            set { mSilentError = value; }
        }
        #endregion


        #region Compiled Assembly
        private Assembly mCompiledAssembly;
        /// <summary>
        /// Gets reference to the assembly that was compiled.
        /// </summary>
        public Assembly CompiledAssembly
        {
            get { return mCompiledAssembly; }
        }
        #endregion

        #region Compiled Type
        private Type mCompiledType;
        /// <summary>
        /// Gets reference to the type that was compiled.
        /// </summary>
        public Type CompiledType
        {
            get { return mCompiledType; }
        }
        #endregion

        #region Has Error
        /// <summary>
        /// Checks whether pasring and compilation was successful.
        /// </summary>
        public bool HasError
        {
            get
            {
                return mErrors != null ? mErrors.HasErrors : false;
            }
        }
        #endregion

        #region Errors
        private CompilerErrorCollection mErrors;
        /// <summary>
        /// Gets reference to error collection.
        /// </summary>
        public CompilerErrorCollection Errors
        {
            get { return mErrors; }
        }
        #endregion

        #endregion

        #region Parse And Compile
        /// <summary>
        /// Parse the source template and compile it.
        /// </summary>
        /// <param name="source">Text source of BLADE template.</param>
        public void ParseAndCompile(string source)
        {
            var reader = new CharReader(source);
            ParseAndCompile(reader);
        }

        /// <summary>
        /// Parse the source template and compile it.
        /// </summary>
        /// <param name="reader">The reader responsible for reading characters.</param>
        public void ParseAndCompile(ICharReader reader)
        {
            string codes = "";
            Exception error = null;

            #region Parse and generate code from source template

            try
            {
                codes = Generate(reader);
            }
            catch(Exception ex)
            {
                error = ex;
            }

            if (error != null && !mSilentError) throw error;
            #endregion

            #region Compile generated code
            if (error == null)
            {
                try
                {
                    CSharpCodeProvider provider = new CSharpCodeProvider();
                    CompilerParameters parameters = new CompilerParameters();

                    //add referenced assemblies
                    var lst = ReferencedAssemblies;
                    if (lst != null && lst.Count > 0)
                    {
                        for (int i = 0; i < lst.Count; i++)
                        {
                            parameters.ReferencedAssemblies.Add(lst[i]);
                        }
                    }

                    //settings
                    parameters.GenerateInMemory = mGenerateInMemory;

                    //compile
                    var compileResults = provider.CompileAssemblyFromSource(parameters, codes);

                    //process results
                    if (!compileResults.Errors.HasErrors)
                    {
                        #region Success

                        mCompiledAssembly = compileResults.CompiledAssembly;

                        //find the compiled type
                        string typeName = Namespace + "." + ClassName;
                        mCompiledType = mCompiledAssembly.GetType(typeName);

                        #endregion
                    }
                    else
                    {
                        #region Error

                        mErrors = compileResults.Errors;

                        //throw new Exception("Compilation failed.");

                        //throw the first error if not silent
                        if (!mSilentError)
                        {
                            //find first error
                            CompilerError firstError = null;
                            for (int i = 0; i < mErrors.Count; i++)
                            {
                                firstError = mErrors[i];
                                if (!firstError.IsWarning) break;
                            }

                            //build message
                            string message = firstError.ErrorText;
                            message += string.Format(" (Row: {0} Column: {1}", firstError.Line, firstError.Column);

                            //throw
                            throw new Exception(message);
                        }

                        #endregion
                    }
                }
                catch
                {
                    if (!mSilentError) throw;
                }
            }
            #endregion
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Create a new instance.
        /// </summary>
        public BladeCodeCompiler()
            : base()
        {
            mReferencedAssemblies = new List<string>();
            mReferencedAssemblies.Add("system.dll");
            mReferencedAssemblies.Add("Thn.dll");
            mReferencedAssemblies.Add("Thn.Net.dll");
            mReferencedAssemblies.Add("Thn.Net.Http.dll");
            mReferencedAssemblies.Add("Thn.Web.dll");
            mReferencedAssemblies.Add("Thn.Web.Mvc.dll");
            mReferencedAssemblies.Add("Thn.Web.Blade.dll");
        }
        #endregion
    }
}
